#include "evaluator.h"
#include <fstream>
#include <cstdlib>
#include <iostream>

using namespace std;

/* weights for different stages */
Weights WEIGHTS[NUM_STAGES];

/* evaluate the given player's number of moves
   #in#  gs             ... current gameState
   #in#  player         ... current player
   #ret#                ... number of moves the player has                  */
int evaluateMobility(gameState gs, BoardState player)
{
    BitBoard self, opponent, empty, result, temp;
    
    if (player == BLACK)
    {
        self = gs.black;
        opponent = gs.white;
    }
    else
    {
        self = gs.white;
        opponent = gs.black;
    }
    empty = ~(self | opponent | BLOCKED_MAP);
    
    result.top = result.bottom = 0;
    
    for (int i = 0; i < MAX_DIRECTION; i++)
    {   
        temp = (self << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i] & opponent;
        
        while (temp != 0)
        {    
            temp = (temp << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i];   
            result = result | (temp & empty);          
            temp = temp & opponent;
        }
    }
    
    int mobility = BitCount(result.top) + BitCount(result.bottom);
    
    return mobility;
}

/* count the number of frontier discs (discs next to empty squares)
   #in#  self           ... bitboard for the color we are evaluating
   #in#  empty          ... bitboard of empty squares
   #ret#                ... number of frontier discs                        */
int evaluateFrontier(BitBoard self, BitBoard empty)
{
    int sum = 0;
    // count the number of empty squares in each direction and add them up
    for (int i = 0; i < MAX_DIRECTION; i++)
    {
        BitBoard temp = (self << DIRECTION_SHIFT[i]) & DIRECTION_MASK[i] & empty;
        
        sum += BitCount(temp.top) + BitCount(temp.bottom);   
        
        self = self & ~(temp >> DIRECTION_SHIFT[i]);
    }
    return sum;
}

/* calculate the how many more pieces white has over black */
int evaluatePieceDiff(gameState gs)
{
    return BitCount(gs.white.top) + BitCount(gs.white.bottom)
           - BitCount(gs.black.top) - BitCount(gs.black.bottom);
}

/* evaluate the gameState
   #in#  gs             ... current gameState
   #in#  player         ... current player
   #ret#                ... integer value of the gameState,
                            positive means favouring WHITE                  */
int evaluateGame(gameState gs, BoardState player, int stage)
{
    Feature_Values feature_value = GetFeatureValue(gs, player);
    
    return (int) evaluateGameL(feature_value, stage);
}

int evaluateTerminal(gameState gs)
{
    int disc_diff = evaluatePieceDiff(gs);
    
    if (disc_diff > 0)
        return disc_diff * TERMINAL_DIFF_WEIGHT + WIN_WEIGHT;
    else if (disc_diff < 0)
        return -(disc_diff * TERMINAL_DIFF_WEIGHT + WIN_WEIGHT);
    else
        return 0;
}

Feature_Values GetFeatureValue(gameState gs, BoardState player)
{
    BitBoard empty = ~(gs.black | gs.white | BLOCKED_MAP);
    
    // test for terminal state
    int m_black = evaluateMobility(gs, BLACK);
    int m_white = evaluateMobility(gs, WHITE);
    
    Feature_Values v;
    v.disc_diff = evaluatePieceDiff(gs);
    v.mobility = m_white - m_black;
    v.frontier = evaluateFrontier(gs.white, empty) - evaluateFrontier(gs.black, empty);
    v.player_is_white = (player == WHITE) ? 1 : 0;
    
    for (int i = 0; i < 15; i++)
    {
        v.disc_feature[i] = BitCount(gs.white & DISC_FEATURE[i]) - BitCount(gs.black & DISC_FEATURE[i]);
    }
    
    return v;
}

/* evaluate difference between number of stable discs
   #in#  gs             ... current gameState
   #ret#                ... white_stable - black_stable                     */
int evaluateStabilityDiff(gameState gs)
{
    return 0;
}

/* evaluate the gameState using pre-computed feature values */
float evaluateGameL(Feature_Values v, int stage)
{
    float result = 0;
    float * w = &(WEIGHTS[stage].disc_feature[0]);
    int * value = &(v.disc_feature[0]);
    
    for (int i = 0; i < NUM_FEATURES; i++)
    {
        result += w[i] * value[i];
    }
    return result;
}

/* initialise weights by reading from a file
   set up memory used to store the game information                         */
void initialiseEvaluator()
{
    fstream filestr;
    
    filestr.open(WEIGHT_FILE, fstream::in);
    
    if (filestr.is_open())
    {
        char buffer[100];
        
        for (int i = 0; i < NUM_FEATURES; i++)
        {
            
            for (int j = 0; j < NUM_STAGES; j++)
            {
                float * w = &(WEIGHTS[j].disc_feature[0]);
                filestr.getline(buffer, 100);
                w[i] = atof(buffer);    
            }
        } 
    }
    else
    {
        // assign random weights
        for (int i = 0; i < NUM_STAGES; i++)
        {
            float * w = &(WEIGHTS[i].disc_feature[0]);
            for (int j = 0; j < NUM_FEATURES; j++)
            {
                w[j] = rand() % 100 - 50;
            }
        }
    }
    filestr.close();
    
}

void printWeights()
{
    for (int i = 0; i < NUM_STAGES - 1; i++)
    {
        cout << i+1 << ".cor = " << WEIGHTS[i].disc_feature[0];
        cout << "  X = "<< WEIGHTS[i].disc_feature[5];
        cout << "  mob = " << WEIGHTS[i].mobility;
        cout << "  fr = " << WEIGHTS[i].frontier;
        cout << "  dd = " << WEIGHTS[i].disc_diff << endl;
    }
}

/* write weights back into file */
void writeWeights(char * filename)
{
    fstream filestr;
    if (filename == NULL)
        filestr.open(WEIGHT_FILE, fstream::out);   
    else
        filestr.open(filename, fstream::out);
    
    if(filestr.is_open())
    {
        for (int i = 0; i < NUM_FEATURES; i++)
        {
            for (int j = 0; j < NUM_STAGES; j++)
            {
                float * w = &(WEIGHTS[j].disc_feature[0]);
                filestr << w[i] << endl;
            }
        }
    }
    filestr.close();
}
